﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;

namespace uk.ac.soton.ses.DAL
{
    public partial class ExperimentParameter
    {
        /// <summary>
        /// Add an existing parameter as a child of the parameter
        /// </summary>
        /// <param name="dbcontext">The db context being used</param>
        /// <param name="childParam">The child parameter</param>
        /// <returns>The link created between parent and child param</returns>
        public ExperimentParameterLink AddChildParameter(MaterialsMetadataContext dbcontext, ExperimentParameter childParam)
        {
            ExperimentParameter parentParam = this;

            //See if the link already exists
            ExperimentParameterLink paramLink = dbcontext.ExperimentParameterLinks.FirstOrDefault(link => link.ExperimentParameterParent.ID == parentParam.ID && link.ExperimentParameterLinked.ID == childParam.ID);

            //It doesn't exist, so create it
            if (paramLink == null)
            {
                paramLink = new ExperimentParameterLink()
                {
                    ExperimentParameterParent = parentParam,
                    ExperimentParameterLinked = childParam
                };
                dbcontext.AddToExperimentParameterLinks(paramLink);
            }
            return paramLink;
        }

        /// <summary>
        /// Create a new parameter that has the same properties as the source, useful for templates
        /// </summary>
        /// <param name="recordParent">Whether we want to record the ID of the parameter we copied from</param>
        /// <returns>The new parameter</returns>
        public ExperimentParameter CreateCopy(bool recordParent)
        {
            //Create a new ExperimentParameter
            ExperimentParameter duplicateParam = new ExperimentParameter();

            //Set its properties to the same as the source
            duplicateParam.Name = this.Name;
            duplicateParam.Value = this.Value;
            duplicateParam.Unit = this.Unit;
            duplicateParam.Type = this.Type;
            duplicateParam.IsCompulsory = this.IsCompulsory;

            //Link to the parameter we copied from
            if (recordParent)
            {
                duplicateParam.SourceExperimentParameterID = this.ID;
            }

            //Return the new parameter
            return duplicateParam;
        }

        /// <summary>
        /// Returns child parameters
        /// </summary>
        /// <returns>An array of child parameters</returns>
        public ExperimentParameter[] GetChildParameters()
        {
            if (!this.ExperimentParameterChildren.IsLoaded)
            {
                this.ExperimentParameterChildren.Load();
            }

            List<ExperimentParameter> allChildParams = new List<ExperimentParameter>();
            foreach (ExperimentParameterLink link in this.ExperimentParameterChildren)
            {
                if (!link.ExperimentParameterLinkedReference.IsLoaded)
                {
                    link.ExperimentParameterLinkedReference.Load();
                }
                allChildParams.Add(link.ExperimentParameterLinked);
            }
            //this.ExperimentParameterChildren.Select(link => link.ExperimentParameterLinked).ToArray();
            
            return allChildParams.ToArray();
        }

        /// <summary>
        /// Returns child parameters matching filtered by a key
        /// </summary>
        /// <param name="key">The name of the parameter to filter on</param>
        /// <returns>An array of child parameters</returns>
        public ExperimentParameter[] GetChildParameters(string key)
        {
            ExperimentParameter[] allChildParams = this.GetChildParameters();
            return allChildParams.Where(param => param.Name == key).ToArray();
        }

        /// <summary>
        /// Return an HTML string that contains the Value of the parameter, but with keywords such as URLs found and replaced with active HTML
        /// </summary>
        /// <returns>An HTML string representing the parameter's value</returns>
        public string GetValueAsParsedHTML()
        {
            List<string[]> regexList = new List<string[]>();
            regexList.Add(
                new string[2]{
                        @"(?i)(?<word>experiment)\s(?<expid>\d+)",
                        @"<a href='/_layouts/HDC/ExperimentDetails.aspx?id=${expid}' target='_blank'>${word} ${expid}</a>"
                    });
            regexList.Add(
                new string[2]{
                        @"(?<url>((mailto\:|(news|(ht|f)tp(s?))\://){1}\S+))",
                        @"<a href='${url}' target='_blank'>${url}</a>"
                    });
            string regexinputstring = (this.Value ?? "");
            foreach (string[] regexsearch in regexList)
            {
                regexinputstring = Regex.Replace(regexinputstring, regexsearch[0], regexsearch[1]);
            }
            return regexinputstring;
        }

        /// <summary>
        /// Get the name of the experiment with its identifier
        /// </summary>
        /// <param name="idFirst">Whether the ID is placed before the name</param>
        /// <param name="prepend">A string to prepend</param>
        /// <param name="append">A string to append</param>
        /// <returns>A string in the format: "prepend" + "experiment_name [ID]" + "append"</returns>
        public string GetNameAndID(bool idFirst = false, string prepend = "", string append = "")
        {
            string returnString = "";
            if (idFirst)
            {
                returnString = string.Format("[{0}] {1}", this.ID, this.Name);
            }
            else
            {
                returnString = string.Format("{1} [{0}]", this.ID, this.Name);
            }

            return prepend + returnString + append;
        }

        /// <summary>
        /// Return a string that describes the parameter's path, taking into account parent parameters and the parent experiment
        /// </summary>
        /// <param name="dbcontext">The database context being used</param>
        /// <param name="includeSelf">Whether to include the source parameter in the path</param>
        /// <returns>The parameter's path</returns>
        public string GetParameterNamePath(MaterialsMetadataContext dbcontext, bool includeSelf)
        {
            if (!this.ExperimentReference.IsLoaded)
            {
                this.ExperimentReference.Load();
            }

            //Generate the parent parameter chain
            List<ExperimentParameter> parentParameterChain = this.GetParameterChain(dbcontext, includeSelf);

            List<string> pathChain = new List<string>();

            //Generate the rest of the parameter chain items
            pathChain.AddRange(parentParameterChain.Select(param => param.Name));

            //Join the parameter names together and prepend the experiment name
            return this.Experiment.GetNameAndID() + " - " + string.Join("\\", pathChain.ToArray());
        }

        /// <summary>
        /// Return a string that describes the parameter's path with the IDs of the parent experiment and parent parameters
        /// </summary>
        /// <param name="dbcontext">The database context being used</param>
        /// <param name="includeSelf">Whether to include the source parameter in the path</param>
        /// <returns>The parameter's path</returns>
        public string GetParameterIDPath(MaterialsMetadataContext dbcontext, bool includeSelf)
        {
            if (!this.ExperimentReference.IsLoaded)
            {
                this.ExperimentReference.Load();
            }

            //Generate the parent parameter chain
            List<ExperimentParameter> parentParameterChain = this.GetParameterChain(dbcontext, includeSelf);

            List<string> pathChain = new List<string>();

            //Generate the parameter chain with IDs
            pathChain.AddRange(parentParameterChain.Select(param => param.ID.ToString()));

            //Join the parameter names together and prepend the experiment ID
            return this.Experiment.ID.ToString() + "-" + string.Join("\\", pathChain.ToArray());
        }

        /// <summary>
        /// Return a list containing all of the parent parameters above this parameter.
        /// The first parameter will be the highest and the last parameter will be the lowest in the chain.
        /// </summary>
        /// <param name="dbcontext">A database context to perform the operation</param>
        /// <param name="includeSelf">Whether the chain includes the source parameter at the end of the chain</param>
        /// <returns>A list containing the chain of parent parameters.</returns>
        public List<ExperimentParameter> GetParameterChain(MaterialsMetadataContext dbcontext, bool includeSelf)
        {
            List<ExperimentParameter> paramChain= new List<ExperimentParameter>();
            ExperimentParameter parent = dbcontext.GetParentParameter(this);
            if (parent != null)
            {
                paramChain.AddRange(parent.GetParameterChain(dbcontext,true));
            }
            if (includeSelf)
            {
                paramChain.Add(this);
            }

            return paramChain;
        }

        /// <summary>
        /// Find all parameters that are defined at the same level as this parameter.
        /// </summary>
        /// <param name="dbcontext">A database context to perform the operation</param>
        /// <param name="matchName">Whether just to match the parameters that have the same name</param>
        /// <returns>An IEnumerable of parameters at the same level</returns>
        public IEnumerable<ExperimentParameter> GetSameLevelParameters(MaterialsMetadataContext dbcontext, bool matchName)
        {
            //Our return data
            IEnumerable<ExperimentParameter> sameLevelParams;

            //Load from the dbcontext in case we were detached elsewhere
            ExperimentParameter parameter = dbcontext.GetExperimentParameterByID(this.ID);

            //Load the related experiment if necessary
            if (!parameter.ExperimentReference.IsLoaded)
            {
                parameter.ExperimentReference.Load();
            }

            //Load the parent parameter
            ExperimentParameter parentParam = dbcontext.GetParentParameter(parameter);

            //If the parent parameter is null, we're at the top level already
            if (parentParam == null)
            {
                //Search in top level parameters
                IEnumerable<ExperimentParameter> topLevelParams = dbcontext.GetExperimentTopLevelParameters(parameter.Experiment.ID, false);
                if (matchName)
                {
                    sameLevelParams = dbcontext.GetExperimentTopLevelParameters(parameter.Experiment.ID, false, parameter.Name);
                }
                else
                {
                    sameLevelParams = dbcontext.GetExperimentTopLevelParameters(parameter.Experiment.ID, false);
                }
            }
            else
            {
                if (matchName)
                {
                    //Search in parameters below parentParam, matching the name
                    sameLevelParams = parentParam.GetChildParameters(parameter.Name);
                }
                else
                {
                    //Search in parameters below parentParam
                    sameLevelParams = parentParam.GetChildParameters();
                }
            }

            return sameLevelParams;
        }
        
        /// <summary>
        /// Returns true if the parameter is a system parameter
        /// </summary>
        public bool IsSystemParameter
        {
            get { return this.Name.StartsWith("HDCSystem"); }
        }

        /// <summary>
        /// Checks the type of the value and whether the required data has been entered
        /// </summary>
        public bool ValueIsValid
        {
            get { return this.ValueIsCompulsoryAndPresent && this.ValueMatchesType; }
        }

        /// <summary>
        /// Returns true if there is a string in the value field and the parameter is compulsory
        /// </summary>
        public bool ValueIsCompulsoryAndPresent
        {
            get
            {
                return (this.IsCompulsory==null || this.IsCompulsory == false || !string.IsNullOrEmpty(this.Value));
            }
        }

        /// <summary>
        /// Returns true if the value contents match the type set
        /// </summary>
        public bool ValueMatchesType
        {
            get
            {
                if (string.IsNullOrEmpty(this.Type) || string.IsNullOrEmpty(this.Value))
                {
                    return true;
                }
                else
                {
                    switch (this.Type.ToLower())
                    {
                        case "short":
                        case "int16":
                            short storedShortValue;
                            return (short.TryParse(this.Value, out storedShortValue));

                        case "int":
                        case "int32":
                        case "integer":
                            int storedIntValue;
                            return (int.TryParse(this.Value, out storedIntValue));

                        case "long":
                        case "int64":
                            long storedLongValue;
                            return (long.TryParse(this.Value, out storedLongValue));

                        case "single":
                        case "float":
                            float storedFloatValue;
                            return (float.TryParse(this.Value, out storedFloatValue));

                        case "double":
                            double storedDoubleValue;
                            return (double.TryParse(this.Value, out storedDoubleValue));

                        case "byte":
                            byte storedByteValue;
                            return (byte.TryParse(this.Value, out storedByteValue));

                        case "bool":
                        case "boolean":
                            bool storedBoolValue;
                            return (bool.TryParse(this.Value, out storedBoolValue));

                        case "char":
                            return (this.Value.Length == 1);

                        default:
                            return true;
                    }
                }
            }
        }
    }
}
